package com.fsq.xmfsubdomain;

import java.awt.event.FocusEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.Reader;
import java.net.InetAddress;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;

import org.xbill.DNS.ARecord;
import org.xbill.DNS.Lookup;
import org.xbill.DNS.MXRecord;
import org.xbill.DNS.NSRecord;
import org.xbill.DNS.Name;
import org.xbill.DNS.Record;
import org.xbill.DNS.Resolver;
import org.xbill.DNS.SOARecord;
import org.xbill.DNS.SimpleResolver;
import org.xbill.DNS.Type;
import org.xbill.DNS.ZoneTransferIn;

import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.scene.control.Label;
import javafx.scene.control.TableView;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;

public class ScanTask extends Task<String> {

	private TreeView<String> id_treeview;
	private TableView<Row> id_tableview;
	private Integer threads;
	private String domain;
	private Label id_status;
	private Integer recursive; // 递归级别
	private Integer baiduNumber; // 百度爬取条数
	private Integer threadsSpider; // 百度、必应等爬虫线程数
	private Resolver resolver;
	private Boolean isLookupCduan;
	private Integer timeout;

	// private HashSet<Record> allRecordSet = new HashSet<Record>(); //
	// 因为不需要重复，所以HashSet
	private Set<Record> allRecordSet = Collections.synchronizedSet(new HashSet<Record>()); // 线程安全的、因为多线程同时修改
	private Set<Record> level3RecordSet = Collections.synchronizedSet(new HashSet<Record>()); // 线程安全的、因为多线程同时修改
	private Set<Record> level4RecordSet = Collections.synchronizedSet(new HashSet<Record>()); // 线程安全的、因为多线程同时修改
	private Set<InetAddress> blacklist;
	private LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<String>(31111);
	private Set<String> ipList = Collections.synchronizedSet(new HashSet<String>()); // 存放ip地址列表，因为不需要重复，所以HashSet
	private LinkedList<String> ipCList = new LinkedList<String>(); // 存在ipList对应的c段的ip地址列表

	// 1、获取常用记录类型。MX NS SOA
	// 2、测试每个dns服务器的区域传送、获取泛域名解析ip列表加入黑名单。
	// 3、通过搜索引擎、获取其他接口查询二级域名 百度、必应、netcraft
	// 4、通过字典爆破二级域名。
	// 5、对获取到的二级域名获取其ip c段
	// 6、对ip段内的地址进行域名反查/aizhan bing等
	// 7、递归（3、4、5级域名可选，仅用字典。）

	public ScanTask(TreeView<String> id_treeview, TableView<Row> id_tableview, Integer threads, String domain,
					Label id_status, Integer recursive, Integer intsearchcount, Integer threadsSpider, Resolver resolver,
					Set<InetAddress> blacklist, Boolean isLookupCduan, Integer timeout) {
		super();
		this.id_treeview = id_treeview;
		this.id_tableview = id_tableview;
		this.threads = threads;
		this.domain = domain;
		this.recursive = recursive;
		this.baiduNumber = intsearchcount;
		this.threadsSpider = threadsSpider;
		this.resolver = resolver;
		this.blacklist = blacklist;
		this.isLookupCduan = isLookupCduan;
		this.id_status = id_status;
		this.timeout = timeout;
	}

	public Set<InetAddress> getBlacklist() {
		return blacklist;
	}

	@SuppressWarnings("unchecked")
	@Override
	protected String call() {

		try {

			// 设置dns服务器及超时
			if (resolver != null) {
				resolver.setTimeout(0, timeout);

				Lookup.setDefaultResolver(resolver);
			} else {
				Lookup.getDefaultResolver().setTimeout(0, timeout);
			}
			// treeview创建一个当前域名的子节点
			TreeItem<String> ti = new TreeItem<String>(domain);

			// 为当前域名创建子节点
			TreeItem<String> tiSOA = new TreeItem<String>("SOA记录");
			TreeItem<String> tiMX = new TreeItem<String>("MX记录");
			TreeItem<String> tiNS = new TreeItem<String>("NS记录");
			TreeItem<String> tiLevel2 = new TreeItem<String>("二级域名");

			ti.getChildren().addAll(tiNS, tiMX, tiSOA, tiLevel2);

			Platform.runLater(new Runnable() {

				@Override
				public void run() {
					id_status.setText("获取MX/NS/SOA记录...");
					id_treeview.getRoot().getChildren().add(ti);
				}
			});

			ObservableList<Row> items = id_tableview.getItems();

			// 1、获取常用记录类型。 NS MX SOA #################################
			Record[] nsRecords = new Lookup(domain, Type.NS).run();
			Record[] mxRecords = new Lookup(domain, Type.MX).run();
			Record[] soaRecords = new Lookup(domain, Type.SOA).run();
			Record[] txtRecords = new Lookup(domain, Type.TXT).run();

			// 添加到总表 NS MX SOA

			if (nsRecords != null) {
				for (Record record : nsRecords) {
					try {
						allRecordSet.add(record);

						String domainname = record.getAdditionalName().toString(true);
						InetAddress address = InetAddress.getByName(domainname);
						String ip = address.getHostAddress();
						ipList.add(ip);
						Row row = new Row(domain, domainname, ip, "DNS", null, "NS记录");
						TreeItem<String> tiRecord = new TreeItem<String>(domainname);
						Platform.runLater(new Runnable() {

							@Override
							public void run() {
								id_status.setText("获取NS记录" + domainname);
								tiNS.getChildren().add(tiRecord);
								items.add(row);
							}
						});
					} catch (Exception e) {

						continue;
					}
				}
			}

			if (mxRecords != null) {
				for (Record record : mxRecords) {

					try {
						allRecordSet.add(record);

						String domainname = record.getAdditionalName().toString(true);
						InetAddress address = InetAddress.getByName(domainname);
						String ip = address.getHostAddress();
						ipList.add(ip);
						Row row = new Row(domain, domainname, ip, "DNS", null, "MX记录");
						TreeItem<String> tiRecord = new TreeItem<String>(domainname);
						Platform.runLater(new Runnable() {

							@Override
							public void run() {
								id_status.setText("获取MX记录" + domainname);
								tiMX.getChildren().add(tiRecord);
								items.add(row);
							}
						});
					} catch (Exception e) {
						continue;
					}
				}
			}

			if (soaRecords != null) {
				for (Record record : soaRecords) {
					try {
						allRecordSet.add(record);
						String domainname1 = ((SOARecord) record).getAdmin().toString(true);
						String domainname2 = ((SOARecord) record).getHost().toString(true);

						InetAddress address1 = InetAddress.getByName(domainname1);
						String ip1 = address1.getHostAddress();
						ipList.add(ip1);

						InetAddress address2 = InetAddress.getByName(domainname2);
						String ip2 = address2.getHostAddress();
						ipList.add(ip2);

						Row row1 = new Row(domain, domainname1, ip1, "DNS", null, "SOA记录");
						TreeItem<String> tiRecord1 = new TreeItem<String>(domainname1);

						Row row2 = new Row(domain, domainname2, ip2, "DNS", null, "SOA记录");
						TreeItem<String> tiRecord2 = new TreeItem<String>(domainname2);
						Platform.runLater(new Runnable() {

							@Override
							public void run() {
								id_status.setText("获取SOA记录" + domainname1 + "	" + domainname2);

								tiSOA.getChildren().add(tiRecord1);
								tiSOA.getChildren().add(tiRecord2);

								items.add(row1);
								items.add(row2);

							}
						});
					} catch (Exception e) {
						continue;
					}
				}
			}

			// 2、获取泛域名解析ip列表同时测试每个dns服务器的区域传送。加入黑名单。#################
			Platform.runLater(new Runnable() {

				@Override
				public void run() {
					id_status.setText("测试区域传送...");
				}
			});

			TreeItem<String> tiWildcard = new TreeItem<String>("泛域名IP");

			if (nsRecords != null) {
				for (Record record : nsRecords) {

					// 测试区域传送
					String dnsServer = record.getAdditionalName().toString(true);
					ZoneTransferIn xfr = ZoneTransferIn.newAXFR(new Name(domain), dnsServer, null);
					xfr.setTimeout(3);
					List records = null;
					try {
						records = xfr.getAXFR();
					} catch (Exception e) {
						Platform.runLater(new Runnable() {

							@Override
							public void run() {
								id_status.setText(dnsServer + "区域传送失败,测试下一个...");
							}
						});
					}

					if (records != null) {

						for (Iterator it = records.iterator(); it.hasNext();) {

							Record r = (Record) it.next();
							if (allRecordSet.add(r)) {
								if (r instanceof ARecord) {
									ARecord ar = (ARecord) r;
									String domainname = ar.getName().toString(true);
									String ip = ar.getAddress().getHostAddress();
									ipList.add(ip);

									Row row = new Row(domain, domainname, ip, "区域传送", null, "A记录");
									TreeItem<String> tiRecord = new TreeItem<String>(domainname);
									Platform.runLater(new Runnable() {

										@Override
										public void run() {
											id_status.setText("区域传送获取到A记录" + domainname);
											tiLevel2.getChildren().add(tiRecord);
											items.add(row);
										}
									});
								}
							}
						}
					}

					// 使用时间生成一个随机域名
					Long l = System.currentTimeMillis();
					String host = l.toString();
					String domainname = host + "." + domain;
					// System.out.println("生成的随机域名" + domainname);
					// 使用目标域的dns服务器解析这个域名
					SimpleResolver res = new SimpleResolver(record.getAdditionalName().toString(false));
					Lookup lookup = new Lookup(domainname);
					lookup.setResolver(res);
					try {
						Record[] Records = lookup.run();
						// 将此地址加入泛域名解析的 ip地址黑名单。
						if (Records != null) {
							for (Record record2 : Records) {
								InetAddress address = ((ARecord) record2).getAddress();
								ipList.add(address.getHostAddress());
								if (blacklist.add(address)) {
									TreeItem<String> tiIp = new TreeItem<String>(address.getHostAddress());
									tiWildcard.getChildren().add(tiIp);
								}
							}
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
			Platform.runLater(new Runnable() {
				@Override
				public void run() {
					id_status.setText("获取泛域名解析ip列表");
					ti.getChildren().add(tiWildcard);
				}
			});

			// 3、通过搜索引擎、获取其他接口查询二级域名#####################################

			// 百度爬虫#################################
			Platform.runLater(new Runnable() {

				@Override
				public void run() {
					id_status.setText("百度抓取二级域名,线程数" + threadsSpider);
				}
			});
			System.out.println("百度抓取中。。。");
			CountDownLatch countDownLatch = new CountDownLatch(threadsSpider);
			for (int i = 0; i < threadsSpider; i++) {
				new Thread(new BaiduSpider(countDownLatch, queue, blacklist, id_treeview, id_tableview, domain,
						id_status, ipList, allRecordSet, tiLevel2)).start();
			}

			// 要访问的百度url地址放入队列
			queue.clear();
			for (int i = 0; i <= baiduNumber; i += 50) {
				String urlBaidu = "https://www.baidu.com/s?wd=site:" + domain + "&rn=50&pn=" + i;
				queue.add(urlBaidu);
			}

			// 等待百度爬虫线程结束。
			System.out.println("等待百度爬虫完成。。。");
			countDownLatch.await();

			// 必应爬虫#############################################
			Platform.runLater(new Runnable() {

				@Override
				public void run() {
					id_status.setText("必应抓取二级域名,线程数" + threadsSpider);
				}
			});
			System.out.println("必应抓取中。。。");
			countDownLatch = new CountDownLatch(threadsSpider);
			for (int i = 0; i < threadsSpider; i++) {
				new Thread(new BingSpider(countDownLatch, queue, blacklist, id_treeview, id_tableview, domain,
						id_status, ipList, allRecordSet, tiLevel2)).start();
			}

			// 要访问的必应url地址放入队列
			queue.clear();
			for (int i = -1; i <= baiduNumber; i += 10) {
				String urlBing = "http://cn.bing.com/search?q=site:" + domain + "&FORM=PERE&first=" + i;
				queue.add(urlBing);
			}

			// 等待必应爬虫线程结束。
			System.out.println("等待必应爬虫完成。。。");
			countDownLatch.await();

			// Netcraft爬虫################################
			System.out.println("Netcraft抓取中,线程数" + threadsSpider);
			Platform.runLater(new Runnable() {

				@Override
				public void run() {
					id_status.setText("Netcraft查询二级域名...");
				}
			});
			countDownLatch = new CountDownLatch(threadsSpider);
			for (int i = 0; i < threadsSpider; i++) {
				new Thread(new NetcraftSpider(countDownLatch, queue, blacklist, id_treeview, id_tableview, domain,
						id_status, ipList, allRecordSet, tiLevel2)).start();
			}

			// 要访问的Netcraft url地址放入队列
			queue.clear();
			for (int i = 1; i <= baiduNumber; i += 20) {
				String urlBing = "http://searchdns.netcraft.com/?restriction=site+ends+with&host=" + domain
						+ "&lookup=wait..&position=limited&from=" + i;
				queue.add(urlBing);
			}

			// 等待Netcraft爬虫线程结束。
			System.out.println("等待Netcraft爬虫完成。。。");
			countDownLatch.await();

			// 4、通过字典爆破二级域名。###########################################
			Platform.runLater(new Runnable() {

				@Override
				public void run() {
					id_status.setText("字典level2.txt爆破二级域名,线程数" + threads);
				}
			});
			String fileName = System.getProperty("user.dir") + File.separator + "dict" + File.separator + "level2.txt";
			Reader fi = new FileReader(fileName);
			BufferedReader br = new BufferedReader(fi);
			String u;

			// 创建爆破线程
			countDownLatch = new CountDownLatch(threads);
			for (int i = 0; i < threads; i++) {
				new Thread(new SubdomainBrute(countDownLatch, queue, blacklist, id_treeview, id_tableview, domain,
						id_status, ipList, allRecordSet, tiLevel2)).start();
			}

			// 读取字典放入队列
			queue.clear();
			while ((u = br.readLine()) != null) {
				Integer count = queue.size();
				System.out.println("当前队列大小" + count);
				if (count > 30000) {
					Thread.sleep(300);
				}
				queue.add(u);
			}

			// 字典读取完成后等待爆破线程结束。
			br.close();
			fi.close();
			countDownLatch.await();
			System.out.println("第五步");

			// 5、对获取到的二级域名获取其ip c段 ########################################
			Platform.runLater(new Runnable() {

				@Override
				public void run() {
					id_status.setText("获取C段ip地址列表...");
				}
			});
			HashSet<String> pre3 = new HashSet<String>();
			for (String string : ipList) {
				int i = string.lastIndexOf(".");
				pre3.add(string.substring(0, i + 1) + "0/24");
			}

			TreeItem<String> tiCduan = new TreeItem<String>("C段:" + pre3.size() + "个");
			for (String string : pre3) {
				// 添加节点到treebiew
				tiCduan.getChildren().add(new TreeItem<String>(string));
				ipCList.addAll(Util.stringToIps(string));
			}
			Platform.runLater(new Runnable() {

				@Override
				public void run() {
					ti.getChildren().add(tiCduan);
				}
			});

			// 6、对ip段内的地址进行域名反查/aizhan bing ####################################
			// 爱站反查################################ 爱站最多600次/ip/天?
			Platform.runLater(new Runnable() {

				@Override
				public void run() {
					id_status.setText("爱站反查二级域名...");
				}
			});
			System.out.println("爱站反查中。。。");
			countDownLatch = new CountDownLatch(1); // 爱站只能单线程查询，否则就会被禁，好无奈
			for (int i = 0; i < 1; i++) {
				new Thread(new ReverseLookupThread(countDownLatch, queue, blacklist, id_treeview, id_tableview, domain,
						id_status, ipList, allRecordSet, tiLevel2)).start();
			}
			// 要反查的ip地址放入队列
			queue.clear();
			System.out.println("爱站查询ip个数：" + ipList.size());
			for (String ip : ipList) { // 爱站限制了每天查询次数，所以就不查c段了
				queue.add(ip);
			}
			// 等待ip反查线程结束。
			System.out.println("等待ip反查线程完成。。。");
			countDownLatch.await();

			// 必应反查############

			if (isLookupCduan) {
				Platform.runLater(new Runnable() {

					@Override
					public void run() {
						id_status.setText("必应反查C段二级域名,线程数" + threadsSpider);
					}
				});
				System.out.println("必应反查C段中。。。");
				countDownLatch = new CountDownLatch(threadsSpider);
				for (int i = 0; i < threadsSpider; i++) {
					new Thread(new BingReverseLookup(countDownLatch, queue, blacklist, id_treeview, id_tableview,
							domain, id_status, ipList, allRecordSet, tiLevel2)).start();
				}
				// 要反查的ip地址放入队列
				queue.clear();
				for (String ip : ipCList) {
					queue.add(ip);
				}
				// 等待必应反查线程结束。
				System.out.println("等待必应反查线程完成。。。");
				countDownLatch.await();
			}

			// 7、递归（仅用字典。）################################################
			// 三级域名爆破
			if (recursive > 2) {
				Platform.runLater(new Runnable() {

					@Override
					public void run() {
						id_status.setText("字典爆破三级域名,线程数" + threads);
					}
				});
				String fileName3 = System.getProperty("user.dir") + File.separator + "dict" + File.separator
						+ "level3.txt";

				String u3;
				System.out.println("开始查询三级域名");
				TreeItem<String> tiLevel3 = new TreeItem<String>("三级域名");
				Platform.runLater(new Runnable() {
					@Override
					public void run() {
						ti.getChildren().add(tiLevel3);
					}
				});
				// 创建爆破线程
				countDownLatch = new CountDownLatch(threads);
				for (int i = 0; i < threads; i++) {
					new Thread(new Level3Brute(countDownLatch, queue, blacklist, id_treeview, id_tableview, domain,
							id_status, ipList, level3RecordSet, tiLevel3)).start();
				}

				// 跑三级域名
				queue.clear();
				HashSet<String> hs = new HashSet<String>();
				for (Record record : allRecordSet) {

					if (record instanceof ARecord) {
						ARecord ar = (ARecord) record;
						String level2name = ar.getName().toString(true);
						if (!hs.add(level2name)) {
							continue;
						}
						// 读取字典放入队列
						Reader fi3 = new FileReader(fileName3);
						BufferedReader br3 = new BufferedReader(fi3);
						while ((u3 = br3.readLine()) != null) {
							Integer count = queue.size();
							// System.out.println("当前队列大小" + count);
							if (count > 30000) {
								Thread.sleep(300);
							}
							queue.add(u3 + "." + level2name);
							// System.out.println("三级字典" + u3);
						}

						br3.close();
						fi3.close();
					}
				}
				// 字典读取完成后等待三级域名爆破线程结束。

				System.out.println("等待三级爆破结束");
				countDownLatch.await();
				System.out.println("三级域名爆破结束");
				int size = tiLevel3.getChildren().size();
				Platform.runLater(new Runnable() {

					@Override
					public void run() {
						tiLevel3.setValue("三级域名:"+size+"个");
					}
				});
			}

			// 四级域名爆破
			if (recursive > 3) {
				Platform.runLater(new Runnable() {

					@Override
					public void run() {
						id_status.setText("字典爆破四级域名,线程数" + threads);
					}
				});
				String fileName4 = System.getProperty("user.dir") + File.separator + "dict" + File.separator
						+ "level4.txt";

				String u4;
				System.out.println("开始查询四级域名");
				TreeItem<String> tiLevel4 = new TreeItem<String>("四级域名");
				Platform.runLater(new Runnable() {
					@Override
					public void run() {
						ti.getChildren().add(tiLevel4);
					}
				});
				// 创建爆破线程 // 跑四级域名
				countDownLatch = new CountDownLatch(threads);
				for (int i = 0; i < threads; i++) {
					new Thread(new Level4Brute(countDownLatch, queue, blacklist, id_treeview, id_tableview, domain,
							id_status, ipList, level4RecordSet, tiLevel4)).start();
				}
				HashSet<String> hs = new HashSet<String>();
				queue.clear();
				for (Record record : level3RecordSet) {

					if (record instanceof ARecord) {
						ARecord ar = (ARecord) record;
						String level3name = ar.getName().toString(true);
						// 读取字典放入队列
						if (!hs.add(level3name)) {
							continue;
						}
						Reader fi4 = new FileReader(fileName4);
						BufferedReader br4 = new BufferedReader(fi4);
						while ((u4 = br4.readLine()) != null) {
							Integer count = queue.size();
							// System.out.println("当前队列大小" + count);
							if (count > 30000) {
								Thread.sleep(300);
							}
							queue.add(u4 + "." + level3name);
						}
						br4.close();
						fi4.close();
					}
				}
				System.out.println("等待四级域名爆破结束");
				countDownLatch.await();
				int size = tiLevel4.getChildren().size();
				Platform.runLater(new Runnable() {

					@Override
					public void run() {
						tiLevel4.setValue("四级域名:"+size+"个");
					}
				});
				System.out.println("四级域名爆破结束");
			}

			// 在treeview中创建ip列表子节点
			int size = tiLevel2.getChildren().size();
			TreeItem<String> tiIpList = new TreeItem<String>("IP列表:" + ipList.size() + "个");
			for (String string : ipList) {
				tiIpList.getChildren().add(new TreeItem<String>(string));
			}

			Platform.runLater(new Runnable() {

				@Override
				public void run() {
					ti.getChildren().add(tiIpList);
					id_status.setText(domain + "子域名收集结束");
					tiLevel2.setValue("二级域名:" + size + "个");
				}
			});

			// System.out.println("三级域名" + level3RecordSet);
			// System.out.println("四级域名" + level4RecordSet);
			System.out.println(domain + "子域名收集结束");

		} catch (Exception e) {
			e.printStackTrace();
		}

		return null;
	}

}
